
namespace Crux.Core.EFCore;
/// <summary>
/// CruxDbContext
/// </summary>
public abstract class CruxDbContext : DbContext
{
    private static readonly IUniqueIdentifierGenerator Generator = new UniqueIdentifierGenerator();
    private readonly HashSet<object> HardDeleteEntities = new HashSet<object>();
    /// <inheritdoc/>
    public CruxDbContext(DbContextOptions options) : base(options)
    {

    }

    /// <inheritdoc/>
    public virtual void Initialize()
    {
        ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;

        ChangeTracker.Tracked += (s, e) =>
        {
            ApplyChange(e.Entry);
        };
        ChangeTracker.StateChanged += (s, e) =>
        {
            ApplyChange(e.Entry);
        };
    }
    /// <inheritdoc/>
    public override EntityEntry Add(object entity)
    {
        entity = GenerateIdentify(entity);

        return base.Add(entity);
    }

    private static object GenerateIdentify(object entity)
    {
        if (entity is IGenericEntity<Guid> guidEntity)
        {
            guidEntity.Id = Generator.Generate<Guid>();
        }
        else if (entity is IGenericEntity<long> longEntity)
        {
            longEntity.Id = Generator.Generate<long>();
        }
        else if (entity is IGenericEntity<string> stringEntity)
        {
            stringEntity.Id = Generator.Generate<string>();
        }

        return entity;
    }

    private static T GenerateIdentify<T>(T entity)
    {
        if (entity is IGenericEntity<Guid> guidEntity)
        {
            guidEntity.Id = Generator.Generate<Guid>();
        }
        else if (entity is IGenericEntity<long> longEntity)
        {
            longEntity.Id = Generator.Generate<long>();
        }
        else if (entity is IGenericEntity<string> stringEntity)
        {
            stringEntity.Id = Generator.Generate<string>();
        }

        return entity;
    }

    /// <inheritdoc/>
    public override EntityEntry<TEntity> Add<TEntity>(TEntity entity)
    {
        entity = GenerateIdentify(entity);

        return base.Add(entity);
    }

    /// <inheritdoc/>
    public override ValueTask<EntityEntry> AddAsync(object entity, CancellationToken cancellationToken = default)
    {
        entity = GenerateIdentify(entity);

        return base.AddAsync(entity, cancellationToken);
    }

    /// <inheritdoc/>
    public override ValueTask<EntityEntry<TEntity>> AddAsync<TEntity>(TEntity entity, CancellationToken cancellationToken = default)
    {
        entity = GenerateIdentify(entity);

        return base.AddAsync(entity, cancellationToken);
    }

    /// <inheritdoc/>
    public override void AddRange(IEnumerable<object> entities)
    {
        foreach (var entity in entities)
        {
            base.Add(GenerateIdentify(entity));
        }
    }
    /// <inheritdoc/>
    public override void AddRange(params object[] entities)
    {
        foreach (var entity in entities)
        {
            base.Add(GenerateIdentify(entity));
        }
    }

    /// <inheritdoc/>
    public override async Task AddRangeAsync(IEnumerable<object> entities, CancellationToken cancellationToken = default)
    {
        foreach (var entity in entities)
        {
            await base.AddAsync(GenerateIdentify(entity), cancellationToken).ConfigureAwait(false);
        }
    }

    /// <inheritdoc/>
    public override async Task AddRangeAsync(params object[] entities)
    {
        foreach (var entity in entities)
        {
            await base.AddAsync(GenerateIdentify(entity)).ConfigureAwait(false);
        }
    }
    void ApplyChange(EntityEntry entityEntry)
    {
        if (entityEntry is null) return;

        switch (entityEntry.State)
        {
            case EntityState.Deleted:
                if (entityEntry.Entity is not ISoftDelete ||
                    HardDeleteEntities.Contains(entityEntry.Entity))
                {
                    return;
                }

                entityEntry.Reload();

                if (entityEntry.Entity is ISoftDelete softDelete)
                {
                    ObjectHelper.TrySetProperty(softDelete, x => x.Deleted, () => true);
                }

                break;
        }
    }
    /// <inheritdoc/>
    public void PhysicsDelete<T, TPrimary>(object entitiy)
        where T : IGenericEntity<TPrimary>, ISoftDelete
    {
        Requires.NotNull(entitiy, nameof(entitiy));

        this.HardDeleteEntities.Add(entitiy);
        this.Remove(entitiy);
    }

    private void ResetEvent()
    {
        var entries = this.ChangeTracker?.Entries()?.Where(e =>
            e.State == EntityState.Added ||
            e.State == EntityState.Modified ||
            e.State == EntityState.Deleted ||
            e.State == EntityState.Unchanged)?.ToList();
        if (entries is not null)
        {
            entries.ForEach(entry =>
            {
                entry.State = EntityState.Detached;
            });
        }
    }

    /// <inheritdoc/>
    public override int SaveChanges()
    {
        ChangeTracker.DetectChanges();

        try
        {
            var count = base.SaveChanges();
            return count;
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new ConflictException("更新冲突");
        }
        finally
        {
            ResetEvent();
        }
    }
    /// <inheritdoc/>
    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        ChangeTracker.DetectChanges();

        try
        {
            var count = base.SaveChanges(acceptAllChangesOnSuccess: acceptAllChangesOnSuccess);
            return count;
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new ConflictException("更新冲突");
        }
        finally
        {
            ResetEvent();
        }
    }
    /// <inheritdoc/>
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        this.ChangeTracker.DetectChanges();

        try
        {
            var count = await base.SaveChangesAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
            return count;
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new ConflictException("更新冲突");
        }
        finally
        {
            this.ResetEvent();
        }
    }
    /// <inheritdoc/>
    public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
    {
        ChangeTracker.DetectChanges();

        try
        {
            var count = await base.SaveChangesAsync(acceptAllChangesOnSuccess: acceptAllChangesOnSuccess, cancellationToken: cancellationToken).ConfigureAwait(false);
            return count;
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new ConflictException("更新冲突");
        }
        finally
        {
            ResetEvent();
        }
    }
}